/*
This file is a part of Awesomium, a library that makes it easy for 
developers to embed web-content in their applications.

Copyright (C) 2009 Adam J. Simmons

Project Website:
<http://princeofcode.com/awesomium.php>

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 
02110-1301 USA
*/

#ifndef __WEBVIEW_H__
#define __WEBVIEW_H__

#include "API/WebViewListener.h"
#include <map>

#if defined(_WIN32)
#include <windows.h>
#pragma warning( disable: 4251 )
#elif defined(__APPLE__)
class NSEvent;
#endif

class WebViewWaitState;
class WebViewProxy;
class JSValueFutureImpl;
class FutureValueCallback;
class CheckKeyboardFocusCallback;
namespace base { class Thread; }
class Lock;
namespace WebViewEvents { class InvokeCallback; }

namespace Awesomium {

	class WebCore;

	/**
	* Mouse button enumerations, used with WebView::injectMouseDown 
	* and WebView::injectMouseUp
	*/
	enum MouseButton {
		LEFT_MOUSE_BTN,
		MIDDLE_MOUSE_BTN,
		RIGHT_MOUSE_BTN
	};

	enum KeyModifier {
		SHIFT_MOD	= 1 << 0,
		CONTROL_MOD	= 1 << 1,
		ALT_MOD 	= 1 << 2,
		META_MOD	= 1 << 3,
		KEYPAD_KEY	= 1 << 4, // If the key is on the keypad (use instead of keypad-specific keycodes)
		AUTOREPEAT_KEY = 1 << 5, // If this is not the first KeyPress event for this key
		SYSTEM_KEY	= 1 << 6 // if the keypress is a system event (WM_SYS* messages in windows)
	};
	/**
	* A simple rectangle class, used with WebView::render
	*/
	struct _OSMExport Rect {
		int x, y, width, height;

		Rect();
		Rect(int x, int y, int width, int height);
		bool isEmpty() const;
	};

  /**
	* A WebView is essentially a single instance of a web-browser (created via the WebCore singleton)
	* that you can interact with (via input injection, javascript, etc.) and render to an off-screen buffer.
	*/
	class _OSMExport WebView
	{
    friend class WebBindings;

	public:

    typedef enum TransparencyMode {
		  TM_OPAQUE                          // No transparency
    , TM_CANVAS_ALPHA_CHANNEL            // Use native Chromium alpha channel (problems under Windows)
    , TM_DEDUCE_ALPHA_CHANNEL            // Render twice to deduce an alpha channel
    , TM_MAX_VALUE
	  };

    typedef enum RenderCopyMode {
      RCM_UPDATE_ALL                     // update all pixels of destination buffer (considered as an image of the same width and height as the view)
    , RCM_UPDATE_DIRTY                   // update only dirty pixels of destination buffer (considered as an image of the same width and height as the view)
    , RCM_DIRTY_ONLY                     // destination buffer is filled with the dirty pixels (buffer is considered as an image of the size of the dirty area)
    };

  /**
		* Explicitly destroys this WebView instance. If you neglect to call this, the WebCore singleton
		* will automatically destroy all lingering WebView instances at shutdown.
		*
		* @note	This should NEVER be called directly from one of the notifications of WebViewListener.
		*/
		void destroy();

		/**
		* Registers a WebViewListener to call upon various events (such as load completions, callbacks, title receptions,
		* cursor changes, etc).
		*
		* @param	listener	The WebViewListener to register. Or, you can pass '0' to undo any current registrations.
		*/
		void setListener(WebViewListener* listener);

		/**
		* Retrieves the current WebViewListener.
		*
		* @return	If a WebViewListener is registered, returns a pointer to the instance, otherwise returns 0.
		*/
		WebViewListener* getListener();

		/**
		* Loads a URL into the WebView asynchronously.
		*
		* @param	url	The URL to load.
		*
		* @param	frameName	Optional, the name of the frame to load the URL in; leave this blank to load in the main frame.
		*/
		void loadURL(const std::string& url, const std::wstring& frameName = L"");

		/**
		* Loads a string of HTML into the WebView asynchronously.
		*
		* @param	html	The HTML string to load.
		*
		* @param	frameName	Optional, the name of the frame to load the HTML in; leave this blank to load in the main frame.
		*
		* @note	The base directory (specified via WebCore::setBaseDirectory) will be used to resolve
		*		relative URL's/resources (such as images, links, etc).
		*/
		void loadHTML(const std::string& html, const std::wstring& frameName = L"");

		/**
		* Loads a local file into the WebView asynchronously.
		*
		* @param	file	The file to load.
		*
		* @param	frameName	Optional, the name of the frame to load the file in; leave this blank to load in the main frame.
		*
		* @note	The file should exist within the base directory (specified via WebCore::setBaseDirectory).
		*/
		void loadFile(const std::string& file, const std::wstring& frameName = L"");

		/**
		* Navigates back/forward in history via a relative offset.
		*
		* @note
		*	For example, to go back one page:
		*		myView->goToHistoryOffset(-1);
		*	Or, to go forward one page:
		*		myView->goToHistoryOffset(1);
		*
		* @param	offset	The relative offset in history to navigate to.
		*/
		void goToHistoryOffset(int offset);

		/**
		* Refresh the current page.
		*/
		void refresh();

    /**
    * MessageLogger interface.
		*/
    struct MessageLogger
    {
      virtual ~MessageLogger() {}
      virtual void log(const char* message) = 0;
    };

		/**
    * Define an external message logger.
    *
    * @param logger can be null.
    *
    * @note
		* Caller is responsible for the deletion of the given 'logger'
    */
    void setMessageLogger(MessageLogger* logger);

    /**
    * Retrieve the message logger.
    *
    * @note
		* The result is null if there isn't a logger.
    */
    MessageLogger* getMessageLogger();

		/**
		* Executes a string of Javascript in the context of the current page asynchronously.
		*
		* @param	javascript	The string of Javascript to execute.
		*
		* @param	frameName	Optional, the name of the frame to execute in; leave this blank to execute in the main frame.
		*/
		void executeJavascript(const std::string& javascript, const std::wstring& frameName = L"");

		/**
		* Executes a string of Javascript in the context of the current page asynchronously with a result.
		*
		* @param	javascript	The string of Javascript to execute.
		*
		* @param	frameName	Optional, the name of the frame to execute in; leave this blank to execute in the main frame.
		*
		* @return	Returns a 'FutureJSValue' which is basically an 'IOU' for the future JSValue result.
		*			You can obtain the actual result via FutureJSValue::get later.
		*/
		Awesomium::FutureJSValue executeJavascriptWithResult(const std::string& javascript, const std::wstring& frameName = L"");

		/**
		* Executes a string of Javascript in the context of the current page asynchronously, and waits for the execution to complete.
		* @param	javascript	The string of Javascript to execute.
		* @param	frameName	Optional, the name of the frame to execute in; leave this blank to execute in the main frame.
		*/
		void syncExecuteJavascript(const std::string& javascript, const std::wstring& frameName = L"");

		/**
		* Sets a global 'Client' property that can be accessed via Javascript from
		* within all pages loaded into this web-view.
		*
		* @param	name	The name of the property.
		* @param	value	The javascript-value of the property.
		*
		* @note	You can access all properties you set via the 'Client' object using Javascript. For example,
		*		if you set the property with a name of 'color' and a value of 'blue', you could access
		*		this from the page using Javascript: document.write("The color is " + Client.color);
		*/
		void setProperty(const std::string& name, const JSValue& value);

		/**
		* Registers a global 'Client' callback that can be invoked via Javascript from
		* within all pages loaded into this view. You will need to register a WebViewListener
		* (WebView::setListener) to receieve notification of callbacks (WebViewListener::onCallback).
		*
		* @param	name	The name of the callback. You can invoke the callback in Javascript
		*					as: Client.your_name_here(arg1, arg2, ...);
		*
		* @note	In the future, it won't be necessary to register the callback name in advance.
		*/
		void setCallback(const std::string& name);

    /**
		* Commands the web-view to do its layout now, in order to know if it's dirty.
    */
    void syncLayout();

		/**
		* Returns whether or not the current web-view is dirty and needs to be re-rendered.
		*
		* @return	If the web-view is dirty, returns true, otherwise returns false.
		*/
		bool isDirty();

		/**
		* Renders the WebView to an off-screen buffer.
		*
		* @param	destination	 The buffer to render to.
    *             If copyMode is RCM_UPDATE_ALL or RCM_UPDATE_DIRTY, its width and height should match the WebView's.
    *             If copyMode is RCM_DIRTY_ONLY, its width and height match 'renderedRect' width and height
    *             One pixel is encoded as 4 bytes (BGRA/RGBA according to WebCore setting)
		*
		* @param	destRowSpan	 Number of bytes in each row of the destination image buffer. (pass -1 to use the 'default' (width*4) bytes row size)
    *             Unused if copyMode is RCM_DIRTY_ONLY.
		*
		* @param	copyMode	Specify the amount of pixels to update in destination;
    *             RCM_UPDATE_ALL updates all the pixels of destination image
    *             RCM_UPDATE_DIRTY updates only the updated (dirty) pixels of destination image
    *             RCM_DIRTY_ONLY copies the updated (dirty) pixels to 'destination' memory, forming an image of 'renderedRect' width & height
		*
		* @param	renderedRect	Optional (pass 0 to ignore);
		*							you can provide a pointer to a Rect to store the dimensions of the 
		*							rendered area, or rather, the dimensions of the area that actually
		*							changed since the last render.
		*/
		void render(unsigned char* destination, int destRowSpan, RenderCopyMode copyMode, Awesomium::Rect* renderedRect = 0);

    void render_begin(unsigned char* destination, int destRowSpan, RenderCopyMode copyMode, Awesomium::Rect* renderedRect = 0);
    void render_end();

		/**
		* Injects a mouse-move event in local coordinates.
		*
		* @param	x	The absolute x-coordinate of the mouse (localized to the WebView).
		* @param	y	The absolute y-coordinate of the mouse (localized to the WebView).
		*/
		void injectMouseMove(int x, int y);

		/**
		* Injects a mouse-down event.
		*
		* @param	button	The button that was pressed.
		*/
		void injectMouseDown(Awesomium::MouseButton button);

		/**
		* Injects a mouse-up event.
		*
		* @param	button	The button that was released.
		*/
		void injectMouseUp(Awesomium::MouseButton button);

		/**
		* Injects a mouse-wheel event.
		*
		* @param	scrollAmountY	The amount of pixels to scroll by in the Y axis.
		* @param	scrollAmountX	The amount of pixels to scroll by in the X axis.
		*/
		void injectMouseWheelXY(int scrollAmountX, int scrollAmountY);

		/**
		* Injects a mouse-wheel event. Calls injectMouseWheelXY(0, scrollAmountY).
		*
		* @param	scrollAmountY	The amount of pixels to scroll by in the Y axis.
		*/
		inline void injectMouseWheel(int scrollAmountY) {
			injectMouseWheelXY(0, scrollAmountY);
		}

		/**
		* Injects a mouse-wheel event.
		*
		* @param	press	True if this is a keypress event, false if this is a release event.
		* @param	modifiers	The bitwise or of appropriate KeyModifier flags for this event.
		* @param	windowsCode	The windows VK_ code for the actual key pressed.
		* @param	nativeCode	Any platform-dependent scancode, or 0 if not available.
		* @param	isSystem	True
		*
		* @see http://www.w3.org/TR/DOM-Level-3-Events/events.html#Events-KeyboardEvent
		*/
		void injectKeyEvent(bool press, int modifiers, int windowsCode, int nativeCode=0);

		/**
		* Injects a mouse-wheel event.
		*
		* @param	text	Text encoded as UTF-32. These should correspond to WM_*CHAR messages, and
		*					should be surrounded Key events unless generated by an IME.
		*/
		void injectTextEvent(std::wstring text);

		// using injectKeyboardEvent is preferred if you have access to the operating system data.

#if defined(_WIN32)
		/**
		* Injects a keyboard event. [Windows]
		*
		* @note	The native Windows keyboard message should be passed, valid message types include:
		*		- WM_KEYDOWN
		*		- WM_KEYUP
		*		- WM_SYSKEYDOWN
		*		- WM_SYSKEYUP
		*		- WM_CHAR
		*		- WM_IMECHAR
		*		- WM_SYSCHAR
		*/
		void injectKeyboardEvent(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam);

#elif defined(__APPLE__)
		/**
		* Injects a keyboard event. [Mac OSX]
		*
		* @note	The native OSX keyboard event should be passed.
		*/
		void injectKeyboardEvent(NSEvent* keyboardEvent);
#endif

		/**
		* Invokes a 'cut' action using the system clipboard.
		*/
		void cut();

		/**
		* Invokes a 'copy' action using the system clipboard.
		*/
		void copy();

		/**
		* Invokes a 'paste' action using the system clipboard.
		*/
		void paste();

		/**
		* Selects all items on the current page.
		*/
		void selectAll();

		/**
		* De-selects all items on the current page.
		*/
		void deselectAll();

		/**
		* Retrieves the content of the current page as plain text.
		*
		* @param	result	The wide string to store the retrieved text in.
		*
		* @param	maxChars	The maximum number of characters to retrieve.
		*
		* @note	Warning: The performance of this function depends upon the number of retrieved characters
		*		and the complexity of the page.
		*/
		void getContentAsText(std::wstring& result, int maxChars);

		/**
		* Zooms into the page, enlarging by 20%.
		*/
		void zoomIn();

		/**
		* Zooms out of the page, reducing by 20%.
		*/
		void zoomOut();

		/**
		* Resets the zoom level.
		*/
		void resetZoom();

		/**
		* Resizes this WebView to certain dimensions.
		*
		* @param	width	The width to resize to.
		* @param	height	The height to resize to.
		*/
		void resize(int width, int height);

		/**
		* Notifies the current page that it has lost focus.
		*/
		void unfocus();

		/**
		* Notifies the current page that is has gained focus.
		*/
		void focus();

		/**
		* Sets whether or not pages should be rendered with a transparent background-color.
		*/
		void setTransparencyMode(TransparencyMode mode);
    TransparencyMode getTransparencyMode();

	protected:
		WebView(int width, int height, bool enableAsyncRendering, int maxAsyncRenderPerSec, base::Thread* coreThread);
		~WebView();

		void startup();
		void setDirty();
		void setAsyncDirty(bool val = true);
		void setFinishRender();
		void setFinishShutdown();
		void setFinishGetContentText();
		void setFinishResize();
    void setFinishGetTransparencyMode();

		void resolveJSValueFuture(int requestID, Awesomium::JSValue* result);
		void handleFutureJSValueCallback(const Awesomium::JSArguments& args);
		void nullifyFutureJSValueCallbacks();
		void handleCheckKeyboardFocus(bool isFocused);

    MessageLogger* logger;

		base::Thread* coreThread;
		WebViewProxy* viewProxy;
		WebViewWaitState* waitState;
		WebViewListener* listener;
		Lock* dirtinessLock;
		bool dirtiness, isKeyboardFocused;
		Lock* jsValueFutureMapLock;
		std::map<int, JSValueFutureImpl*> jsValueFutureMap;

		const bool enableAsyncRendering;

		friend class WebCore;
		friend class ::WebViewProxy;
		friend class ::FutureValueCallback;
		friend class ::CheckKeyboardFocusCallback;
	};

}

#endif
